﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Reflection;
using System.Text.RegularExpressions;

namespace JQuery
{
    [PersistChildren(false)]
    [ParseChildren(true)]
    public abstract class JQueryExtender : Control, IAttributeAccessor
    {
        public bool Enabled { get; set; }

        public bool WaitForPageReady { get; set; }

        public bool RunAgainAfterUpdatePanelRefreshed { get; set; }

        public JQueryExtender()
        {
            Enabled = true;
            propertyMetadata.Add("TargetControlSelector", new JQueryElementAttribute(true));
            foreach (PropertyInfo pi in this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static))
            {
                if (!pi.DeclaringType.IsSubclassOf(typeof(JQueryExtender)))
                    continue;
                JQueryElementAttribute[] attributes = (JQueryElementAttribute[])pi.GetCustomAttributes(typeof(JQueryElementAttribute), true);
                if (attributes.Length == 1)
                {
                    propertyMetadata.Add(pi.Name, attributes[0]);
                    AddMapping(attributes[0].JQueryElementName, pi.Name);
                }
                else
                {
                    propertyMetadata.Add(pi.Name, new JQueryElementAttribute(pi.Name, false));
                    AddMapping(pi.Name, pi.Name);
                }
                MethodInfo getPi = pi.GetGetMethod(true);
                if (getPi != null)
                    getPi.Invoke(this, null);
                else
                    throw new NotSupportedException("The get accessor on the property named " + pi.Name + " could not be found");
            }
        }

        private void AddMapping(string jQueryPropertyName, string serverControlPropetyName)
        {
            if (!jqueryPropertiesMapping.ContainsKey(jQueryPropertyName))
                jqueryPropertiesMapping.Add(jQueryPropertyName, new List<string>());
            jqueryPropertiesMapping[jQueryPropertyName].Add(serverControlPropetyName);
        }

        protected void ChangeMetadata(string propertyName, JQueryElementAttribute attribute)
        {
            if (string.IsNullOrEmpty(attribute.PropertyName))
                attribute.PropertyName = propertyName;
            propertyMetadata[propertyName] = attribute;
        }

        Dictionary<string, JQueryElementAttribute> propertyMetadata = new Dictionary<string, JQueryElementAttribute>();
        Dictionary<string, List<string>> jqueryPropertiesMapping = new Dictionary<string, List<string>>();
        System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
        Dictionary<string, object> properties = new Dictionary<string, object>();

        protected T? GetPropertyValue<T>(string propertyName, T? defaultNullValue)
            where T : struct
        {
            if (properties.ContainsKey(propertyName))
            {
                object propertyValue = properties[propertyName];
                if (propertyValue is T)
                    return (T)propertyValue;
                if (propertyValue == null)
                    return new Nullable<T>();
                if (typeof(T).IsEnum)
                    return (T)Enum.Parse(typeof(T), propertyValue.ToString());
                return (T)Convert.ChangeType(propertyValue, typeof(T));
            }
            SetPropertyValue(propertyName, defaultNullValue);
            return GetPropertyValue(propertyName, defaultNullValue);
        }

        protected T GetPropertyValue<T>(string propertyName, T defaultNullValue)
            where T : class
        {
            if (properties.ContainsKey(propertyName))
            {
                object propertyValue = properties[propertyName];
                if (propertyValue is T)
                    return (T)propertyValue;
                if (propertyValue == null)
                    return null;
                return (T)Convert.ChangeType(propertyValue, typeof(T));
            }
            SetPropertyValue(propertyName, defaultNullValue);
            return GetPropertyValue(propertyName, defaultNullValue);
        }

        protected void SetPropertyValue<T>(string propertyName, T value)
        {
            if (propertyMetadata.ContainsKey(propertyName) && jqueryPropertiesMapping.ContainsKey(propertyMetadata[propertyName].JQueryElementName))
            {
                List<string> properties = jqueryPropertiesMapping[propertyMetadata[propertyName].JQueryElementName];
                if (properties.Count > 1)
                {
                    foreach (string property in properties)
                    {
                        if (this.properties.ContainsKey(property) && this.properties[property] != null)
                            throw new NotSupportedException(string.Format("The property named '{0}' is mapped more than once ({2}) with the jquery property '{1}', thus, you may not define it twice with difference values", propertyName, propertyMetadata[propertyName].JQueryElementName, string.Join(",", properties.ToArray())));
                    }
                }
                this.properties[propertyName] = propertyMetadata[propertyName].GetValue(this, value);
            }
            else
                this.properties[propertyName] = value;
        }

        protected abstract string jQueryFunction { get; }

        public string TargetControlSelector { get; set; }

        public string RenderFunctionCall()
        {
            StringBuilder functionCallBuilder = new StringBuilder("$('");
            JQueryElementAttribute attribute;

            if (propertyMetadata.ContainsKey("TargetControlSelector"))
                functionCallBuilder.Append(propertyMetadata["TargetControlSelector"].GetValue(this, TargetControlSelector));
            else
                functionCallBuilder.Append(TargetControlSelector);
            functionCallBuilder.Append("').");
            functionCallBuilder.Append(jQueryFunction);
            functionCallBuilder.Append("(");
            if (properties.Count > 0)
                functionCallBuilder.Append("{");
            foreach (KeyValuePair<string, object> param in properties)
            {
                if (propertyMetadata.ContainsKey(param.Key))
                    attribute = propertyMetadata[param.Key];
                else
                    attribute = new JQueryElementAttribute(param.Key, false);

                string value = null;
                if (param.Value != null)
                    value = param.Value.ToString();
                if (value == null)
                    continue;
                if (param.Value is bool || param.Value.GetType().IsEnum)
                    value = value.ToLower();
                if (value == "true" || value == "false")
                    attribute.SerializeAs = SerializeAs.Is;
                if (!attribute.IsMatch(value))
                {
                    if (attribute.IsSelector)
                        throw new ArgumentException(string.Format("The selector '{0}' provided for the property {1} does not meet the requirements", value, param.Key));
                    else
                        throw new ArgumentException(string.Format("The value '{0}' provided for the property {1} does not meet the requirements : {2}", value, param.Key, attribute.RegexValidation));

                }
                if (attribute.SerializeAs == SerializeAs.String)
                    functionCallBuilder.Append(attribute.JQueryElementName + ": '" + value + "', ");
                else
                {
                    functionCallBuilder.Append(attribute.JQueryElementName + ": ");
                    if (param.Value.GetType().IsPrimitive || param.Value is string)
                        functionCallBuilder.Append(value);
                    else
                        functionCallBuilder.Append(serializer.Serialize(param.Value));
                    functionCallBuilder.Append(", ");
                }
            }
            functionCallBuilder.Remove(functionCallBuilder.Length - 2, 2);
            if (properties.Count > 0)
                functionCallBuilder.Append(" }");
            functionCallBuilder.Append(");");

            return functionCallBuilder.ToString();
        }

        protected override void Render(HtmlTextWriter writer)
        {
            if (!Enabled)
                return;

            string functionCall = RenderFunctionCall();

            if (WaitForPageReady)
                ScriptManager.RegisterStartupScript(this, GetType(), this.UniqueID, "$(function(){" + functionCall + "});\n", true);
            else
                ScriptManager.RegisterStartupScript(this, GetType(), this.UniqueID, functionCall + "\n", true);

            if (RunAgainAfterUpdatePanelRefreshed)
                ScriptManager.RegisterStartupScript(this, GetType(), this.UniqueID + "(reload)", functionCall, true);

        }

        #region IAttributeAccessor Members

        public string GetAttribute(string key)
        {
            return GetPropertyValue<string>(key, null);
        }

        public void SetAttribute(string key, string value)
        {
            SetPropertyValue(key, value);
        }

        #endregion
    }
}
